package dk.au.cs.peterbn.patterns;

import dk.au.cs.peterbn.patterns.impl.DesignPatternImplFactory;
import dk.au.cs.peterbn.xml.XmlProvider;
import org.jdom.Document;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.jdom.transform.JDOMSource;
import org.xml.sax.SAXException;

import javax.xml.validation.Schema;
import javax.xml.validation.Validator;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * A pattern provider for serialized patterns
 */

public class PatternProvider {


    private List<String> paths;

    private Map<String, DesignPattern> patternCache;
    private DesignPatternImplFactory implFactory;
    private Schema patternSchema;


    /**
     * Construct a new PatternProvider
     */
    public PatternProvider() {
        this.paths = new ArrayList<String>();
        this.patternCache = new HashMap<String, DesignPattern>();
        paths.add("patterns");
        implFactory = new DesignPatternImplFactory();


        //The modular loading process has failed. This will have to do.
        patternSchema = XmlProvider.getPatternSchema();

    }

    /**
     * Get a DesignPattern instance from a canonical pattern identifier.
     * An identifier is on the form {@code <group>:<name>}
     *
     * @param identifier The identifier on the form {@code <group>:<name>}
     * @return A DesignPattern instance
     * @throws Exception if the identifier is malformed or lookup goes wrong
     */
    public DesignPattern getDesignPattern(String identifier) throws Exception {
        if (patternCache.containsKey(identifier)) {
            return patternCache.get(identifier);
        } else {
            String[] path = identifier.split(":");
            if (path.length == 2) {
                //Look for a variation
                String[] localname = path[1].split("/");
                return getDesignPattern(path[0], localname);
            } else {
                throw new IllegalPatternIdentifierException(identifier);
            }
        }
    }


    private DesignPattern getDesignPattern(String group, String[] localname) throws JDOMException, IOException, SAXException {
        assert localname.length >= 1 : "Must supply at least a name for the pattern";
        String patternName = localname[0];
        InputStream patternInputStream = null;
        InputStream variationInputStream = null;

        try {

            String resourceName = group + File.separator + patternName + ".xml";
            for (String path : paths) {
                closeStream(patternInputStream);
                patternInputStream = getClass().getClassLoader().getResourceAsStream(path + File.separator + resourceName);
            }

            SAXBuilder builder = new SAXBuilder();
            if (patternInputStream != null) {
                Validator validator = patternSchema.newValidator();

                Document doc = builder.build(patternInputStream);
                validator.validate(new JDOMSource(doc));
                implFactory.buildDesignPattern(doc);

            } else {
                throw new InvalidPatternException("Unable to locate pattern description for pattern " + group + ":" + patternName);
            }

            //Process any declared variations
            for (int i = 1; i < localname.length; i++) {
                String varName = localname[i];
                String varResourceName = group + File.separator + patternName + "_" + varName + ".xml";
                for (String path : paths) {
                    closeStream(patternInputStream);
                    variationInputStream = getClass().getClassLoader().getResourceAsStream(path + File.separator + varResourceName);
                }
                if (variationInputStream != null) {

                    Validator varValidator = patternSchema.newValidator();
                    Document varDoc = builder.build(variationInputStream);

                    if (varDoc != null) {
                        varValidator.validate(new JDOMSource(varDoc));
                        implFactory.applyVariation(varDoc);
                    }

                } else {
                    throw new InvalidPatternException("Unable to locate pattern variation description for variation " + group + ":" + patternName + "/" + varName);
                }

            }
        } finally {
            closeStream(patternInputStream);
            closeStream(variationInputStream);
        }

        DesignPattern designPattern = implFactory.getPattern();
        patternCache.put(designPattern.getIdentifier(), designPattern);
        return designPattern;
    }

    /**
     * Helper method to close of a stream, if it exists
     *
     * @param result The stream to close
     */
    private void closeStream(InputStream result) {
        if (result != null) {
            try {
                result.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

}
